import get from 'lodash-es/get.js';
import { ref } from 'vue';
import SparkMD5 from '../../plugins/spark-md5.js';
import { uploadFile, uploadChunkFile } from '../../api/index.js';

function handleError(error, defaultMsg) {
  if (error instanceof Error) {
    return error;
  }
  const msg = typeof error === 'string' ? error : defaultMsg;
  return new Error(msg);
}

function getUploadState(onProgress, onSuccess, onFail) {
  const state = {
    done: null,
    fail: null,
    onProgress: onProgress,
    onSuccess: onSuccess,
    onFail: onFail,
    result: {
      progress: ref(0),
      error: ref(null),
      data: ref(null),
      ready: null,
    },
    handleProgress: (progress) => {
      if (progress === true) {
        progress = 100;
      }
      if (Number.isFinite(progress)) {
        let val = state.result.progress.value + progress;
        if (!Number.isFinite(val)) {
          return;
        }
        state.result.progress.value = Math.min(val, 100);
        if (typeof state.onProgress === 'function') {
          state.onProgress(state.result.progress.value);
        }
      }
    },
  };
  state.result.ready = new Promise((resolve, reject) => {
    state.done = (data) => {
      state.result.data.value = data;
      state.handleProgress(true);
      if (typeof state.onSuccess === 'function') {
        state.onSuccess(state.result);
      }
      resolve(state.result);
    };
    state.fail = (msg) => {
      state.result.error.value = msg;
      state.result.data.value = null;
      if (typeof state.onFail === 'function') {
        state.onFail(state.result);
      }
      reject(state.result);
    };
  });
  return state;
}

function getParams(params, extraParams) {
  if (typeof params === 'function') {
    params = params(extraParams);
    return params;
  } else {
    return Object.assign({}, params, extraParams);
  }
}

function getFormData(params) {
  const formData = new FormData();
  Object.keys(params).forEach((key) => {
    formData.append(key, params[key]);
  });
  return formData;
}

function getChunkSize(file) {
  const fileSize = file.size;
  let chunkSize = 0;
  if (fileSize <= 5 * 1024 * 1024) {
    // 0-5M，不分片
    chunkSize = fileSize;
  } else if (fileSize <= 20 * 1024 * 1024) {
    // 5-20M，每个分片大小1M
    chunkSize = 1024 * 1024;
  } else if (fileSize <= 50 * 1024 * 1024) {
    // 20-50M，每个分片大小2M
    chunkSize = 2 * 1024 * 1024;
  } else if (fileSize <= 100 * 1024 * 1024) {
    // 50-100M，每个分片大小4M
    chunkSize = 4 * 1024 * 1024;
  } else if (fileSize <= 200 * 1024 * 1024) {
    // 100-200M，每个分片大小6M
    chunkSize = 6 * 1024 * 1024;
  } else if (fileSize <= 500 * 1024 * 1024) {
    // 200-500M，每个分片大小10M
    chunkSize = 10 * 1024 * 1024;
  } else if (fileSize <= 1024 * 1024 * 1024) {
    // 500M-1G，每个分片大小20M
    chunkSize = 20 * 1024 * 1024;
  } else {
    // 1G以上，每个分片大小20M
    chunkSize = 20 * 1024 * 1024;
  }
  return chunkSize;
}

async function getChunkHash(chunk, options) {
  options = Object.assign({}, options);
  const reader = new FileReader();
  const promise = new Promise((resolve, reject) => {
    reader.onload = (e) => {
      const spark = new SparkMD5.ArrayBuffer();
      spark.append(e.target.result);
      if (typeof options.onload === 'function') {
        options.onload(e);
      }
      resolve(spark.end());
    };
    reader.onerror = (e) => {
      // debugger;
      if (typeof options.onerror === 'function') {
        options.onerror(e);
      }
      reject(e);
    };
  });
  reader.readAsArrayBuffer(chunk);
  return await promise;
}

export async function getFileChunks(file) {
  const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
  const spark = new SparkMD5.ArrayBuffer();
  const chunkSize = getChunkSize(file);
  const chunkPages = Math.ceil(file.size / chunkSize);
  const fileSize = file.size; // 文件大小
  const fileName = file.name;

  async function loadChunk(index) {
    const start = index * chunkSize;
    const end = start + chunkSize >= fileSize ? fileSize : start + chunkSize;
    const chunk = blobSlice.call(file, start, end);
    // console.log('loadNext current chunk: ', chunk);
    const hash = await getChunkHash(chunk, {
      onload: (e) => spark.append(e.target.result),
    });
    return { chunk, hash, index };
  }

  const chunks = await Promise.all(
    new Array(chunkPages).fill(null).map((n, i) => {
      return loadChunk(i);
    })
  );

  const fileHash = spark.end();

  return { fileName, fileSize, fileHash, chunks, chunkSize, chunkPages };
}

export function useUploadChunk({ file, api, params, onProgress, onSuccess, onFail }) {
  async function uploader(filename, fileHash, chunks, chunkPages, api, params) {
    const file = chunks.shift();
    const option = getParams(params, { ...file, filename, fileHash, chunkPages });
    const formData = getFormData(option);
    try {
      let { error, data } = await api(formData);
      if (error) {
        return handleError(error, '文件上传失败');
      }
      state.handleProgress(Math.floor(100 / chunkPages));
      if (chunks.length > 0) {
        data = await uploader(filename, fileHash, chunks, chunkPages, api, params);
      }
      return data;
    } catch (e) {
      return handleError(e);
    }
  }
  async function start(file, api, params) {
    const { fileName, fileHash, chunks, chunkPages } = await getFileChunks(file);
    if (chunks.length > 0) {
      return await uploader(fileName, fileHash, chunks, chunkPages, api, params);
    } else {
      return handleError('文件分片错误');
    }
  }

  let state = getUploadState(onProgress, onSuccess, onFail);

  (async (file, api, params) => {
    const data = await start(file, api, params);
    data instanceof Error ? state.fail(data.message) : state.done(data);
    state = null;
  })(file, api, params);

  return state.result;
}

export function useUploadFile({ file, api, params, onProgress, onSuccess, onFail }) {
  async function start(file, api, params) {
    params = getParams(params, { file });
    const formData = getFormData(params);
    let { error, data } = await api(formData);
    if (error) {
      return handleError(error, '文件上传失败');
    }
    return data;
  }

  let state = getUploadState(onProgress, onSuccess, onFail);

  (async (file, api, params) => {
    const data = await start(file, api, params);
    data instanceof Error ? state.fail(data.message) : state.done(data);
    state = null;
  })(file, api, params);

  return state.result;
}

export function getChunkUploadParams(data, extraParams) {
  const params = {
    file: data.chunk,
    filename: data.filename,
    hash: data.hash,
    chunk: data.index,
    total_chunk: data.chunkPages,
    identifier: data.fileHash,
    filePath: data.filePath,
  };
  return getParams(extraParams, params);
}

export function getUploadParams(data, extraParams) {
  const params = {
    file: data.file,
    filePath: data.filePath,
  };
  return getParams(extraParams, params);
}

export function useUploadApi(useChunk) {
  return async function (formData) {
    let api = useChunk === true ? uploadChunkFile : uploadFile;
    let data;
    try {
      let res = await api(formData).catch(() => null);
      if (res && res.code === 200) {
        data = {
          data: { url: get(res, 'data.url'), filename: get(res, 'data.name') },
        };
      } else {
        data = { error: '上传失败' };
      }
    } catch (e) {
      data = { error: e.message || '上传失败' };
    }
    return data;
  };
}

export function useUploader({ file, api, params, onProgress, onSuccess, onFail }) {
  const chunkSize = getChunkSize(file);
  const useChunk = chunkSize < file.size;
  if (!api || typeof api !== 'function') {
    api = useUploadApi(useChunk);
  }
  if (useChunk) {
    const getParam = (file) => {
      return getChunkUploadParams(file, params);
    };
    return useUploadChunk({ file, api, params: getParam, onProgress, onSuccess, onFail });
  } else {
    const getParam = ({ file }) => {
      return getUploadParams({ file }, params);
    };
    return useUploadFile({ file, api, params: getParam, onProgress, onSuccess, onFail });
  }
}
